package com.rc.saas.tenant.config;

import com.rc.saas.tenant.shiro.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPoolConfig;

import javax.servlet.Filter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro主配置
 * shiro-redis开源插件必需jedis驱动，所以单独设置
 * applicationContext-shiro.xml
 * Created by sven on 2019/10/6
 */
@Configuration
@AutoConfigureAfter({SpringConfig.class, RedisConfig.class, ShiroLifecycleBeanPostProcessorConfig.class})
public class ShiroConfig {
    private static final String PRINCIPAL_ID_FIELD_NAME = "tenantUserId";
    private static final String SESSION_KEY_PREFIX = "saas:tenant:shiro:session:";
    private static final String CACHE_KEY_PREFIX = "saas:tenant:shiro:cache:";

    @Value("${spring.redis.jedis.pool.max-active}")
    private int redisPoolMaxActive;

    //@Value("${spring.redis.jedis.pool.max-wait}")
    //private int redisPoolMaxWait;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int redisPoolMaxIdle;

    @Value("${spring.redis.jedis.pool.min-idle}")
    private int redisPoolMinIdle;

    @Autowired
    private RedisProperties redisProperties;

    //@Autowired
    //private JedisPoolConfig jedisPoolConfig;

    //@Autowired
    //private CustomFilterChainDefinitions customFilterChainDefinitions;

    /**
     * 配置LogoutFilter
     */
    //@Bean
    //public CustomLogoutFilter customLogoutFilter() {
    //    CustomLogoutFilter shiroLogoutFilter = new CustomLogoutFilter();
    //配置登出后重定向的地址，等出后配置跳转到登录接口
    //shiroLogoutFilter.setRedirectUrl("/login");
    //return shiroLogoutFilter;
    //}

    /**
     * shirFilter过滤器ShiroFilterFactoryBean
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 同时securityManager 绑定到 SecurityUtils
        SecurityUtils.setSecurityManager(securityManager);

        // <!-- 要求登录时的链接 -->
        //<property name="loginUrl" value="/login"/>
        //<!-- 登陆成功后要跳转的连接 -->
        //<property name="successUrl" value="/welcome"/>
        //<!-- 没有权限要跳转的链接 -->
        //<property name="unauthorizedUrl" value="/login"/>
        //
        //<!--自定义拦截器-->
        //<property name="filters">
        //    <map>
        //        <entry key="ajax" value-ref="ajaxFilter"/>
        //    </map>
        //</property>

        // 配置登录页面,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 配置登录成功进入的页面URL
        shiroFilterFactoryBean.setSuccessUrl("/welcome");
        // 配置没有权限跳转的URL
        shiroFilterFactoryBean.setUnauthorizedUrl("/login");

        // 静态过虑器链（上面的覆盖下面的）
        /*
        /login=anon
        /logout=logout
        /goLoginning=anon
        /captcha/*=anon

        <!-- anon前端资源不需登录可以访问 -->
        /css/**=anon
        /img/**=anon
        /appjs/**=anon
        /libjs/**=anon
        /static/**=anon
        /include/**=anon
        /templates/**=anon
        /locales/**=anon
        /favicon.ico=anon

        <!--authc需登录可以访问-->
        /welcome=authc
        /welcome2=authc
        / = authc
        */
        //Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        //anon不需登录就可以访问
        //filterChainDefinitionMap.put("/login", "anon");
        //filterChainDefinitionMap.put("/logout", "logout");
        //filterChainDefinitionMap.put("/goLoginning", "anon");
        //filterChainDefinitionMap.put("/captcha/*", "anon");

        //anon前端资源不需登录可以访问
        //filterChainDefinitionMap.put("/css/**", "anon");
        //filterChainDefinitionMap.put("/img/**", "anon");
        //filterChainDefinitionMap.put("/appjs/**", "anon");
        //filterChainDefinitionMap.put("/libjs/**", "anon");
        //filterChainDefinitionMap.put("/static/**", "anon");
        //filterChainDefinitionMap.put("/include/**", "anon");
        //filterChainDefinitionMap.put("/templates/**", "anon");
        //filterChainDefinitionMap.put("/locales/**", "anon");
        //filterChainDefinitionMap.put("/favicon.ico", "anon");

        //authc需登录才可以访问
        //filterChainDefinitionMap.put("/welcome", "authc");
        //filterChainDefinitionMap.put("/welcome2", "authc");

        //兜底
        //filterChainDefinitionMap.put("/", "authc");
        //shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //自定义拦截器
        Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();

        //扩展authc拦截器
        //配置自定义登出 覆盖 logout 之前默认的LogoutFilter
        //filtersMap.put("logout", new CustomLogoutFilter());

        filtersMap.put("authc", new CustomFormAuthenticationFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);

        //将数据库中的访问控制数据追加到xml配置文件的后面
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionSource().getObject());

        return shiroFilterFactoryBean;
    }


    /**
     * 将数据库中的访问控制数据追加到xml配置文件的后面
     * @return
     */
    @Bean
    public CustomFilterChainDefinitions filterChainDefinitionSource() {
        return new CustomFilterChainDefinitions();
    }

    /**
     * 配置自定义认证器
     * @return
     */
    @Bean
    public CustomRealm customRealm() {
        //<!--自定义realm-->
        //<bean id="customRealm" class="com.rc.saas.platform.shiro.CustomRealm">
        //    <property name="cachingEnabled" value="false"/>
        //</bean>

        CustomRealm customRealm = new CustomRealm();
        customRealm.setCachingEnabled(false);
        return customRealm;
    }

    /**
     * 配置securityManager 安全管理器
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        //<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        //    <property name="sessionManager" ref="sessionManager"/>
        //    <property name="cacheManager" ref="cacheManager"/>
        //
        //    <!--自定义realm-->
        //    <property name="realm" ref="customRealm"/>
        //</bean>

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm
        securityManager.setRealm(customRealm());
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(shiroRedisCacheManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        //注入记住我管理器;
        //securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * Session Manager
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean
    public SessionManager sessionManager() {
        //<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        //    <property name="sessionDAO" ref="redisSessionDAO"/>
        //</bean>

        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());

        // 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话
        //sessionManager.setSessionValidationInterval(sessionTimeoutClean);
        //sessionManager.setSessionValidationSchedulerEnabled(true);

        //这一句是关键
        sessionManager.setSessionIdCookie(sessionIdCookie());
        //sessionManager.setSessionIdCookieEnabled(true);

        //是否去掉shiro登录时url里的JSESSIONID
        //sessionManager.setSessionIdUrlRewritingEnabled(true);
        //shiro session监听器
        sessionManager.setSessionListeners(Arrays.asList(new CustomSessionListenerAdapter()));

        return sessionManager;
    }

    /**
     * 配置redis连接池的配置 JedisPoolConfig
     * @return
     */
    @Bean
    public JedisPoolConfig jedisPoolConfig2() {
        //<!-- redis连接池的配置 -->
        //<bean id="jedisPoolConfig2" class="redis.clients.jedis.JedisPoolConfig">
        //    <property name="maxTotal" value="${redis.session.pool.maxTotal}"/>
        //    <property name="maxIdle" value="${redis.session.pool.maxIdle}"/>
        //    <property name="minIdle" value="${redis.session.pool.minIdle}"/>
        //    <property name="testOnBorrow" value="${redis.session.pool.testOnBorrow}"/>
        //    <property name="testOnReturn" value="${redis.session.pool.testOnReturn}"/>
        //</bean>

        JedisPoolConfig jedisPoolConfig2 = new JedisPoolConfig();
        //最大连接数
        jedisPoolConfig2.setMaxTotal(redisPoolMaxActive);

        //最小空闲连接数
        //jedisPoolConfig2.setMaxIdle(redisProperties2.getMaxIdle());
        //当池内没有可用连接时，最大等待时间
        //jedisPoolConfig2.setMaxWaitMillis(redisProperties2.getMaxWait());
        //jedisPoolConfig2.setMinIdle(redisProperties2.getMaxIdle());
        jedisPoolConfig2.setTestOnBorrow(true);
        jedisPoolConfig2.setTestOnReturn(true);
        jedisPoolConfig2.setTestWhileIdle(true);

        return jedisPoolConfig2;
    }

    /**
     * jedis连接工厂
     * @param jedisPoolConfig
     * @return
     */
    //@Bean
    //public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
    //    RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
    //    //设置redis服务器的host或者ip地址
    //    redisStandaloneConfiguration.setHostName(host);
    //    redisStandaloneConfiguration.setPort(port);
    //    //获得默认的连接池构造
    //    //这里需要注意的是，edisConnectionFactoryJ对于Standalone模式的没有（RedisStandaloneConfiguration，JedisPoolConfig）的构造函数，对此
    //    //我们用JedisClientConfiguration接口的builder方法实例化一个构造器，还得类型转换
    //    JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcf = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
    //    //修改我们的连接池配置
    //    jpcf.poolConfig(jedisPoolConfig);
    //    //通过构造器来构造jedis客户端配置
    //    JedisClientConfiguration jedisClientConfiguration = jpcf.build();
    //
    //    return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
    //}

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        //<!-- shiro redisManager -->
        //<bean id="redisManager" class="org.crazycake.shiro.RedisManager">
        //    <property name="host" value="${redis.session.host}:${redis.session.port}"/>
        //    <property name="timeout" value="${redis.session.timeout}"/>
        //    <property name="database" value="0"/>
        //    <property name="jedisPoolConfig" ref="jedisPoolConfig2"/>
        //    <property name="count" value="100"/>
        //</bean>


        RedisManager redisManager = new RedisManager();

        redisManager.setHost(redisProperties.getHost() + ":" + redisProperties.getPort());
        //redisManager.setHost(redisProperties.getHost());
        //redisManager.setPort(port);
        //redisManager.setExpire(1800);// 配置缓存过期时间
        redisManager.setTimeout(Long.valueOf(redisProperties.getTimeout().toMillis()).intValue());
        //redisManager.setTimeout(3000);
        if (StringUtils.isNotEmpty(redisProperties.getPassword())) {
            redisManager.setPassword(redisProperties.getPassword());
        }
        //redisManager.setPassword(redisProperties.getPassword());
        redisManager.setDatabase(0);
        redisManager.setJedisPoolConfig(jedisPoolConfig2());
        //redisManager.setJedisPoolConfig(jedisPoolConfig);
        redisManager.setCount(100);
        return redisManager;
    }

    /**
     * 配置shiro cacheManager
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean(name = "shiroRedisCacheManager")
    public RedisCacheManager shiroRedisCacheManager() {
        //<!--redis管理session-->
        //<bean id="cacheManager" class="org.crazycake.shiro.RedisCacheManager">
        //    <property name="redisManager" ref="redisManager"/>
        //    <!--使用反射来获取auth的redis对象id-->
        //    <property name="principalIdFieldName" value="platformUserId"/>
        //</bean>

        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setKeyPrefix(CACHE_KEY_PREFIX);

        //使用反射来获取auth的redis对象id
        redisCacheManager.setPrincipalIdFieldName(PRINCIPAL_ID_FIELD_NAME);
        return redisCacheManager;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        //<!-- Redis-based session configuration -->
        //<bean id="redisSessionDAO" class="org.crazycake.shiro.RedisSessionDAO">
        //    <property name="redisManager" ref="redisManager"/>
        //    <property name="keyPrefix" value="saas:platform:shiro:session:"/>
        //</bean>

        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setKeyPrefix(SESSION_KEY_PREFIX);
        return redisSessionDAO;
    }

    /**
     * 指定本系统SESSIONID, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID,
     * 当跳出SHIRO SERVLET时如ERROR-PAGE容器会为JSESSIONID重新分配值导致登录会话丢失
     * @return
     */
    @Bean
    //(name = "sessionIdCookie")
    public SimpleCookie sessionIdCookie() {
        //这个参数是cookie的名称
        SimpleCookie simpleCookie = new SimpleCookie("tenant.session.id");
        //防止js读取cookie
        simpleCookie.setHttpOnly(true);
        return simpleCookie;
    }

    /**
     * 配置加密方式
     * @return
     */
    //@Bean
    //public HashedCredentialsMatcher hashedCredentialsMatcher() {
    //    //盐
    //    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    //    //配置散列算法，，使用MD5加密算法
    //    hashedCredentialsMatcher.setHashAlgorithmName("md5");
    //    //设置散列次数
    //    hashedCredentialsMatcher.setHashIterations(1);
    //    return hashedCredentialsMatcher;
    //}

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    //@Bean
    //public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    //    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    //    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    //    return authorizationAttributeSourceAdvisor;
    //}

    /**
     * 保证实现了Shiro内部lifecycle函数的bean执行
     */
    //@Bean
    //public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
    //    return new LifecycleBeanPostProcessor();
    //}
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor as = new AuthorizationAttributeSourceAdvisor();
        as.setSecurityManager(securityManager);
        return as;
    }

}
